package com.wusuowei.common.aspect;

import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson2.JSON;
import com.wusuowei.common.annotation.Log;
import com.wusuowei.common.listener.EventPubListener;
import com.wusuowei.common.model.dto.ClientMsg;
import com.wusuowei.common.model.po.ComOperLog;
import com.wusuowei.common.model.po.ComUser;
import com.wusuowei.common.utils.IpUtil;
import com.wusuowei.common.utils.SecurityUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author wangzhenjun
 * @date 2022/10/26 15:39
 */
@Slf4j
@Aspect
@Component
public class SysOperLogAspect {

    private final Logger logger = LoggerFactory.getLogger(SysOperLogAspect.class);
  //  private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private ThreadLocal<ComOperLog> threadLocal = new ThreadLocal<>();

    @Autowired
    private EventPubListener eventPubListener;

    /**
     * 以注解所标注的方法作为切入点
     */
    @Pointcut("@annotation(com.wusuowei.common.annotation.Log)")
    public void sysLog() {
    }

    /**
     * 前置advice
     * @param joinPoint
     */
    @Before("sysLog()")
    public void before(JoinPoint joinPoint) {
        Log log = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Log.class);
        ComUser user = SecurityUserUtil.getSecurityUser();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //这一步获取到的方法有可能是代理方法也有可能是真实方法
        Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //判断代理对象本身是否是连接点所在的目标对象，不是的话就要通过反射重新获取真实方法
        if (joinPoint.getThis().getClass() != joinPoint.getTarget().getClass()) {
            m = ReflectUtil.getMethod(joinPoint.getTarget().getClass(), m.getName(), m.getParameterTypes());
        }
        //通过真实方法获取该方法的参数名称
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = paramNames.getParameterNames(m);

        //获取连接点方法运行时的入参列表
        Object[] args = joinPoint.getArgs();
        //将参数名称与入参值一一对应起来
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            params.put(parameterNames[i], args[i]);
        }

        ClientMsg clientMsg = IpUtil.getClientMsg(request);

        ComOperLog sysLog = new ComOperLog();
        sysLog.setTitle(log.title());
        sysLog.setBusinessType(log.businessType().ordinal());
        String methodNamePre = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        sysLog.setMethod(methodNamePre + "." + methodName + "()");
        sysLog.setRequestMethod(request.getMethod());
        //sysLog.setOperatorType();
        sysLog.setOperName(user.getUsername());
        sysLog.setDeptName(user.getRole().stream().map(item->item.getName()).collect(Collectors.joining(",")));
        sysLog.setOperUrl(request.getRequestURL().toString());
        sysLog.setOperIp(clientMsg.getIp());
        sysLog.setOperLocation(clientMsg.getAddress());
        sysLog.setOperParam(JSON.toJSONString(params));
        // sysLog.setJsonResult();
        sysLog.setStatus(0);
        //sysLog.setErrorMsg();
        sysLog.setOperTime(LocalDateTime.now());
        //sysLog.setCostTime();
        threadLocal.set(sysLog);
    }

    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @AfterReturning(value = "sysLog()", returning = "responseBody")
    public void doAfter(JoinPoint joinPoint, Object responseBody) {
        LocalDateTime after = LocalDateTime.now();

        ComOperLog sysLog = threadLocal.get();
        LocalDateTime before = sysLog.getOperTime();
        Duration duration = Duration.between(before, after);
        long millis = duration.toMillis();
        sysLog.setCostTime(millis);
        sysLog.setJsonResult(JSON.toJSONString(responseBody));
        // 发布消息
        eventPubListener.pushListener(sysLog);
        //移除当前log实体
        threadLocal.remove();
        logger.info("=======日志发送成功，内容：{}", sysLog);
    }


    /**
     * 异常通知，请求异常会进入到这个方法
     */
    @AfterThrowing(value = "sysLog()", throwing = "throwable")
    public void throwingLogger(Throwable throwable) {

        LocalDateTime after = LocalDateTime.now();

        ComOperLog sysLog = threadLocal.get();
        LocalDateTime before = sysLog.getOperTime();
        Duration duration = Duration.between(before, after);
        long millis = duration.toMillis();
        sysLog.setCostTime(millis);
        sysLog.setJsonResult(JSON.toJSONString(throwable.getMessage()));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //将报错信息写入error字段
        throwable.printStackTrace(new PrintStream(byteArrayOutputStream));
        sysLog.setErrorMsg(byteArrayOutputStream.toString());
        sysLog.setStatus(1);
        // 发布消息
        eventPubListener.pushListener(sysLog);
        //移除当前log实体
        threadLocal.remove();
        logger.info("=======日志发送成功，内容：{}", sysLog);

    }

}

